# ******************************************************************************
#  Copyright (c) 2021-2022. Kneron Inc. All rights reserved.                   *
# ******************************************************************************

# Legacy Module
from .KPStructure import \
    GenericRawImageHeaderBuffer, \
    GenericRawResultHeaderBuffer, \
    GenericRawBypassPreProcImageHeaderBuffer, \
    GenericRawBypassPreProcResultHeaderBuffer

# Core Module
from ...KPBaseClass.ValueBase import ValueRepresentBase, ValueBase
from ...KPStructure import \
    InfCropBoxBuffer
from ...KPValue import \
    InferenceCropBox, \
    HwPreProcInfo, \
    GenericRawResultNDArray

from ...KPEnum import *
from ...KPConstant import Const
from typing import List


class GenericRawImageHeader(ValueBase, ValueRepresentBase):
    """
    Inference descriptor for images.

    Attributes
    ----------
    model_id : int, default=0
        Target inference model ID.
    resize_mode : kp.ResizeMode, default=kp.ResizeMode.KP_RESIZE_ENABLE
        Preprocess resize mode, refer to ResizeMode.
    padding_mode : kp.PaddingMode, default=kp.PaddingMode.KP_PADDING_CORNER
        Preprocess padding mode, refer to PaddingMode.
    normalize_mode : kp.NormalizeMode, default=kp.NormalizeMode.KP_NORMALIZE_KNERON
        Inference normalization, refer to NormalizeMode.
    inference_number : int, default=0
        Inference sequence number.
    inference_crop_box_list : List[kp.InferenceCropBox], default=[]
        Box information to crop.
    width : int, default=0
        Inference image width.
    height : int, default=0
        Inference image height.
    image_format : kp.ImageFormat, default=kp.ImageFormat.KP_IMAGE_FORMAT_RGB565
        Inference image format, refer to ImageFormat.
    """

    def __init__(self,
                 model_id: int = 0,
                 resize_mode: ResizeMode = ResizeMode.KP_RESIZE_ENABLE,
                 padding_mode: PaddingMode = PaddingMode.KP_PADDING_CORNER,
                 normalize_mode: NormalizeMode = NormalizeMode.KP_NORMALIZE_KNERON,
                 inference_number: int = 0,
                 inference_crop_box_list: List[InferenceCropBox] = [],
                 width: int = 0,
                 height: int = 0,
                 image_format: ImageFormat = ImageFormat.KP_IMAGE_FORMAT_RGB565):
        """
        Inference descriptor for images.

        Parameters
        ----------
        model_id : int, default=0
            Target inference model ID.
        resize_mode : kp.ResizeMode, default=kp.ResizeMode.KP_RESIZE_ENABLE
            Preprocess resize mode, refer to ResizeMode.
        padding_mode : kp.PaddingMode, default=kp.PaddingMode.KP_PADDING_CORNER
            Preprocess padding mode, refer to PaddingMode.
        normalize_mode : kp.NormalizeMode, default=kp.NormalizeMode.KP_NORMALIZE_KNERON
            Inference normalization, refer to NormalizeMode.
        inference_number : int, default=0
            Inference sequence number.
        inference_crop_box_list : List[kp.InferenceCropBox], default=[]
            Box information to crop.
        width : int, default=0
            Inference image width.
        height : int, default=0
            Inference image height.
        image_format : kp.ImageFormat, default=kp.ImageFormat.KP_IMAGE_FORMAT_RGB565
            Inference image format, refer to ImageFormat.
        """
        ValueBase.__init__(self,
                           element_buffer_class=GenericRawImageHeaderBuffer,
                           width=width,
                           height=height,
                           resize_mode=resize_mode,
                           padding_mode=padding_mode,
                           image_format=image_format,
                           normalize_mode=normalize_mode,
                           inference_number=inference_number,
                           model_id=model_id,
                           inference_crop_box_buffer_list=GenericRawImageHeader.__cast_list_to_buffer_list(
                               inference_crop_box_list=inference_crop_box_list))

    @staticmethod
    def __cast_list_to_buffer_list(inference_crop_box_list: List[InferenceCropBox]) -> List[InfCropBoxBuffer]:
        return [InfCropBoxBuffer(crop_box_index=inference_crop_box.crop_box_index,
                                 x=inference_crop_box.x,
                                 y=inference_crop_box.y,
                                 width=inference_crop_box.width,
                                 height=inference_crop_box.height) for inference_crop_box in inference_crop_box_list]

    def _cast_element_buffer(self) -> None:
        pass

    @property
    def width(self) -> int:
        """
        int: Inference image width.
        """
        return self._element_buffer._width

    @width.setter
    def width(self, value: int):
        self._element_buffer._width = value

    @property
    def height(self) -> int:
        """
        int: Inference image height.
        """
        return self._element_buffer._height

    @height.setter
    def height(self, value: int):
        self._element_buffer._height = value

    @property
    def resize_mode(self) -> ResizeMode:
        """
        kp.ResizeMode: Preprocess resize mode, refer to ResizeMode.
        """
        return ResizeMode(self._element_buffer._resize_mode)

    @resize_mode.setter
    def resize_mode(self, value: ResizeMode):
        self._element_buffer._resize_mode = value.value

    @property
    def padding_mode(self) -> PaddingMode:
        """
        kp.PaddingMode: Preprocess padding mode, none or auto refer to PaddingMode.
        """
        return PaddingMode(self._element_buffer._padding_mode)

    @padding_mode.setter
    def padding_mode(self, value: PaddingMode):
        self._element_buffer._padding_mode = value.value

    @property
    def image_format(self) -> ImageFormat:
        """
        kp.ImageFormat: Inference image format, refer to ImageFormat.
        """
        return ImageFormat(self._element_buffer._image_format)

    @image_format.setter
    def image_format(self, value: ImageFormat):
        self._element_buffer._image_format = value.value

    @property
    def normalize_mode(self) -> NormalizeMode:
        """
        kp.NormalizeMode: Inference normalization, refer to NormalizeMode.
        """
        return NormalizeMode(self._element_buffer._normalize_mode)

    @normalize_mode.setter
    def normalize_mode(self, value: NormalizeMode):
        self._element_buffer._normalize_mode = value.value

    @property
    def inference_number(self) -> int:
        """
        int: Inference sequence number.
        """
        return self._element_buffer._inference_number

    @inference_number.setter
    def inference_number(self, value: int):
        self._element_buffer._inference_number = value

    @property
    def model_id(self) -> int:
        """
        int: Target inference model ID.
        """
        return self._element_buffer._model_id

    @model_id.setter
    def model_id(self, value):
        self._element_buffer._model_id = value

    @property
    def crop_count(self) -> int:
        """
        int: Number of crop box.
        """
        return self._element_buffer._crop_count

    @property
    def inference_crop_box_list(self) -> List[InferenceCropBox]:
        """
        List[kp.InferenceCropBox]: Box information to crop.
        """
        inference_crop_box_buffer_list = list(self._element_buffer._inf_crop)[:self.crop_count]

        inference_crop_box_list = [InferenceCropBox(
            crop_box_index=inference_crop_box_buffer._crop_number,
            x=inference_crop_box_buffer._x1,
            y=inference_crop_box_buffer._y1,
            width=inference_crop_box_buffer._width,
            height=inference_crop_box_buffer._height
        ) for inference_crop_box_buffer in inference_crop_box_buffer_list]

        return inference_crop_box_list

    # todo. not allow user setting it -> private it.
    @inference_crop_box_list.setter
    def inference_crop_box_list(self, value: List[InferenceCropBox] = []):
        self._element_buffer._crop_count = len(value)
        self._element_buffer._inf_crop = (InfCropBoxBuffer * Const.MAX_CROP_BOX.value)(*[
            InfCropBoxBuffer(crop_box_index=inference_crop_box.crop_box_index,
                             x=inference_crop_box.x,
                             y=inference_crop_box.y,
                             width=inference_crop_box.width,
                             height=inference_crop_box.height) for inference_crop_box in value])

    def get_member_variable_dict(self) -> dict:
        member_variable_dict = {
            'image': {
                'width': self.width,
                'height': self.height,
                'image_format': str(self.image_format)
            },
            'inference_configuration': {
                'resize_mode': str(self.resize_mode),
                'padding_mode': str(self.padding_mode),
                'normalize_mode': str(self.normalize_mode),
                'inference_number': self.inference_number,
                'model_id': self.model_id,
                'crop_count': self.crop_count,
                'inference_crop_box_list': {},
            }
        }

        for idx, inference_crop_box_element in enumerate(self.inference_crop_box_list):
            member_variable_dict['inference_configuration']['inference_crop_box_list'][
                idx] = inference_crop_box_element.get_member_variable_dict()

        return member_variable_dict


class GenericRawResultHeader(ValueBase, ValueRepresentBase):
    """
    Inference raw output descriptor.

    Attributes
    ----------
    inference_number : int, default=0
        Inference sequence number.
    crop_number : int, default=0
        Crop box sequence number.
    num_output_node : int, default=0
        Total number of output nodes.
    product_id : int, default=0
        USB PID (Product ID).
    hw_pre_proc_info : kp.HwPreProcInfo, default=kp.HwPreProcInfo()
        Hardware preprocess info.
    """

    def __init__(self,
                 inference_number: int = 0,
                 crop_number: int = 0,
                 num_output_node: int = 0,
                 product_id: int = 0,
                 hw_pre_proc_info: HwPreProcInfo = HwPreProcInfo()):
        """
        Inference raw output descriptor.

        Parameters
        ----------
        inference_number : int, default=0
            Inference sequence number.
        crop_number : int, default=0
            Crop box sequence number.
        num_output_node : int, default=0
            Total number of output nodes.
        product_id : int, default=0
            USB PID (Product ID).
        hw_pre_proc_info : kp.HwPreProcInfo, default=kp.HwPreProcInfo()
            Hardware preprocess info.
        """
        ValueBase.__init__(self,
                           element_buffer_class=GenericRawResultHeaderBuffer,
                           inference_number=inference_number,
                           crop_number=crop_number,
                           num_output_node=num_output_node,
                           product_id=product_id,
                           hw_pre_proc_info=hw_pre_proc_info._get_element_buffer())

    def _cast_element_buffer(self) -> None:
        pass

    @property
    def inference_number(self) -> int:
        """
        int: Inference sequence number.
        """
        return self._element_buffer._inference_number

    @property
    def crop_number(self) -> int:
        """
        int: Crop box sequence number.
        """
        return self._element_buffer._crop_number

    @property
    def num_output_node(self) -> int:
        """
        int: Total number of output nodes.
        """
        return self._element_buffer._num_output_node

    @property
    def product_id(self) -> int:
        """
        int: USB PID (Product ID).
        """
        return self._element_buffer._product_id

    @property
    def hw_pre_proc_info(self) -> HwPreProcInfo:
        """
        kp.HwPreProcInfo: Hardware preprocess info
        """
        hw_pre_proc_info = HwPreProcInfo()
        hw_pre_proc_info._element_buffer = self._element_buffer._hw_pre_proc_info
        return hw_pre_proc_info

    def get_member_variable_dict(self) -> dict:
        return {
            'inference_number': self.inference_number,
            'crop_number': self.crop_number,
            'num_output_node': self.num_output_node,
            'product_id': '0x{:X}'.format(self.product_id),
            'hw_pre_proc_info': self.hw_pre_proc_info.get_member_variable_dict()
        }


class GenericRawResult(ValueRepresentBase):
    """
    Generic inference raw result.

    Attributes
    ----------
    header : kp.GenericRawResultHeader
        Inference raw output descriptor.
    raw_result : kp.GenericRawResultNDArray
        Inference raw result buffer.
    """

    def __init__(self, buffer_size: int):
        """
        Generic inference raw result.

        Parameters
        ----------
        buffer_size : int
            Size of generic inference raw result buffer.
        """
        self.__header = GenericRawResultHeader()
        self.__raw_result = GenericRawResultNDArray(buffer_size=buffer_size)

    @property
    def header(self) -> GenericRawResultHeader:
        """
        kp.GenericRawResultHeader: Inference raw output descriptor.
        """
        return self.__header

    @property
    def raw_result(self) -> GenericRawResultNDArray:
        """
        kp.GenericRawResultNDArray: Inference raw result buffer.
        """
        return self.__raw_result

    def get_member_variable_dict(self) -> dict:
        return {
            'header': self.header.get_member_variable_dict(),
            'raw_result': self.raw_result.get_member_variable_dict()
        }


class GenericRawBypassPreProcImageHeader(ValueBase, ValueRepresentBase):
    """
    Inference descriptor for images bypass pre-processing.

    Attributes
    ----------
    model_id : int, default=0
        Target inference model ID.
    inference_number : int, default=0
        Inference sequence number.
    image_buffer_size : int, default=0
        Inference image buffer size.
    """

    def __init__(self,
                 model_id: int = 0,
                 inference_number: int = 0,
                 image_buffer_size: int = 0):
        """
        Inference descriptor for images bypass pre-processing.

        Attributes
        ----------
        model_id : int, default=0
            Target inference model ID.
        inference_number : int, default=0
            Inference sequence number.
        image_buffer_size : int, default=0
            Inference image buffer size.
        """
        ValueBase.__init__(self,
                           element_buffer_class=GenericRawBypassPreProcImageHeaderBuffer,
                           model_id=model_id,
                           inference_number=inference_number,
                           image_buffer_size=image_buffer_size)

    def _cast_element_buffer(self) -> None:
        pass

    @property
    def inference_number(self) -> int:
        """
        int: Inference sequence number.
        """
        return self._element_buffer._inference_number

    @inference_number.setter
    def inference_number(self, value: int):
        self._element_buffer._inference_number = value

    @property
    def model_id(self) -> int:
        """
        int: Target inference model ID.
        """
        return self._element_buffer._model_id

    @model_id.setter
    def model_id(self, value):
        self._element_buffer._model_id = value

    @property
    def image_buffer_size(self) -> int:
        """
        int: Inference image buffer size.
        """
        return self._element_buffer._image_buffer_size

    @image_buffer_size.setter
    def image_buffer_size(self, value):
        self._element_buffer._image_buffer_size = value

    def get_member_variable_dict(self) -> dict:
        return {
            'inference_number': self.inference_number,
            'model_id': self.model_id,
            'image_buffer_size': self.image_buffer_size
        }


class GenericRawBypassPreProcResultHeader(ValueBase, ValueRepresentBase):
    """
    Inference raw output descriptor for bypass pre-processing.

    Attributes
    ----------
    inference_number : int, default=0
        Inference sequence number.
    crop_number : int, default=0
        Crop box sequence number.
    num_output_node : int, default=0
        Total number of output nodes.
    product_id : int, default=0
        USB PID (Product ID).
    """

    def __init__(self,
                 inference_number: int = 0,
                 crop_number: int = 0,
                 num_output_node: int = 0,
                 product_id: int = 0):
        """
        Inference raw output descriptor for bypass pre-processing.

        Parameters
        ----------
        inference_number : int, default=0
            Inference sequence number.
        crop_number : int, default=0
            Crop box sequence number.
        num_output_node : int, default=0
            Total number of output nodes.
        product_id : int, default=0
            USB PID (Product ID).
        """
        ValueBase.__init__(self,
                           element_buffer_class=GenericRawBypassPreProcResultHeaderBuffer,
                           inference_number=inference_number,
                           crop_number=crop_number,
                           num_output_node=num_output_node,
                           product_id=product_id)

    def _cast_element_buffer(self) -> None:
        pass

    @property
    def inference_number(self) -> int:
        """
        int: Inference sequence number.
        """
        return self._element_buffer._inference_number

    @property
    def crop_number(self) -> int:
        """
        int: Crop box sequence number.
        """
        return self._element_buffer._crop_number

    @property
    def num_output_node(self) -> int:
        """
        int: Total number of output nodes.
        """
        return self._element_buffer._num_output_node

    @property
    def product_id(self) -> int:
        """
        int: USB PID (Product ID).
        """
        return self._element_buffer._product_id

    def get_member_variable_dict(self) -> dict:
        return {
            'inference_number': self.inference_number,
            'crop_number': self.crop_number,
            'num_output_node': self.num_output_node,
            'product_id': '0x{:X}'.format(self.product_id)
        }


class GenericRawBypassPreProcResult(ValueRepresentBase):
    """
    Generic inference raw result for bypass pre-processing.

    Attributes
    ----------
    header : kp.GenericRawBypassPreProcResultHeader
        Inference raw output descriptor for bypass pre-processing.
    raw_result : kp.GenericRawResultNDArray
        Inference raw result buffer.
    """

    def __init__(self, buffer_size: int):
        """
        Generic inference raw result for bypass pre-processing.

        Parameters
        ----------
        buffer_size : int
            Size of generic inference raw result buffer.
        """
        self.__header = GenericRawBypassPreProcResultHeader()
        self.__raw_result = GenericRawResultNDArray(buffer_size=buffer_size)

    @property
    def header(self) -> GenericRawBypassPreProcResultHeader:
        """
        kp.GenericRawBypassPreProcResultHeader: Inference raw output descriptor for bypass pre-processing.
        """
        return self.__header

    @property
    def raw_result(self) -> GenericRawResultNDArray:
        """
        kp.GenericRawResultNDArray: Inference raw result buffer.
        """
        return self.__raw_result

    def get_member_variable_dict(self) -> dict:
        return {
            'header': self.header.get_member_variable_dict(),
            'raw_result': self.raw_result.get_member_variable_dict()
        }
